package net.itren.jetpackdemo.utils.log

import android.annotation.TargetApi
import android.content.Intent
import android.database.Cursor
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import net.itren.jetpackdemo.BuildConfig
import org.json.JSONArray
import org.json.JSONObject
import java.util.*

object ProjectLog {
    var isVerboseLogEnable = false
    var isDebugLogEnable = false
    var isInfoLogEnable = false
    var isWarnLogEnable = false
    var isErrorLogEnable = true
    private var isAssertLogEnable = false
    var isCustomLogEnable = false

    //用于分析函数的执行时间
    private val tmStarts = HashMap<String, Long>()
    private val timeLoggers = HashMap<String, TimingLogger>()
    fun setFullLogLevel() {
        isCustomLogEnable = true
        isErrorLogEnable = true
        isDebugLogEnable = true
        isInfoLogEnable = true
        isVerboseLogEnable = true
        isWarnLogEnable = true
        isAssertLogEnable = true
    }

    fun setDebugLogLevel() {
        isCustomLogEnable = true
        isErrorLogEnable = true
        isDebugLogEnable = true
        isInfoLogEnable = true
        isVerboseLogEnable = true
        isWarnLogEnable = true
    }

    fun setReleaseLogLevel() {
        isCustomLogEnable = false
        isErrorLogEnable = true
        isDebugLogEnable = false
        isInfoLogEnable = false
        isVerboseLogEnable = false
        isWarnLogEnable = false
    }

    fun setProductionLogLevel() {
        isCustomLogEnable = false
        isErrorLogEnable = false
        isDebugLogEnable = false
        isInfoLogEnable = false
        isVerboseLogEnable = false
        isWarnLogEnable = false
    }

    fun isAssertLogEnable(): Boolean {
        return isAssertLogEnable
    }

    fun setAssertLogEnable(isEnable: Boolean) {
        isAssertLogEnable = true
    }

    fun wtf(tag: String?, msg: String?, throwable: Throwable?) {
        if (isAssertLogEnable()) {
            Log.wtf(tag, msg, throwable)
        }
    }

    fun wtf(clazz: Class<*>, msg: String?, throwable: Throwable?) {
        if (isAssertLogEnable()) {
            wtf(clazz.simpleName, msg, throwable)
        }
    }

    fun wtf(tag: String?, throwable: Throwable?) {
        if (isAssertLogEnable()) {
            wtf(tag, "", throwable)
        }
    }

    fun wtf(clazz: Class<*>, throwable: Throwable?) {
        if (isAssertLogEnable()) {
            wtf(clazz.simpleName, "", throwable)
        }
    }

    fun e(clazz: Class<*>, msg: String?) {
        e(clazz.simpleName, msg)
    }

    fun e(clazz: Class<*>, msg: String?, t: Throwable?) {
        e(clazz.simpleName, msg, t)
    }

    fun e(tag: String?, msg: String?) {
        if (isErrorLogEnable) {
            Log.e(tag, msg!!)
        }
    }

    fun e(tag: String?, msg: String?, t: Throwable?) {
        if (isErrorLogEnable) {
            Log.e(tag, msg, t)
        }
    }

    //log for lazy people.
    //eg: HaizhiLog.e(TAG, "value1 = %f, value2 = %s", var1, var2);
    fun e(tag: String?, msg: String?, vararg args: Any?) {
        e(tag, formatString(msg, *args))
    }

    fun d(clazz: Class<*>, msg: String?) {
        d(clazz.simpleName, msg)
    }

    fun d(clazz: Class<*>, msg: String?, t: Throwable?) {
        d(clazz.simpleName, msg, t)
    }

    fun d(tag: String?, msg: String?) {
        if (isDebugLogEnable) {
            Log.d(tag, msg!!)
        }
    }

    fun d(tag: String?, msg: String?, t: Throwable?) {
        if (isDebugLogEnable) {
            Log.d(tag, msg, t)
        }
    }

    fun d(tag: String?, msg: String?, vararg args: Any?) {
        d(tag, formatString(msg, *args))
    }

    fun i(clazz: Class<*>, msg: String?) {
        i(clazz.simpleName, msg)
    }

    fun i(clazz: Class<*>, msg: String?, t: Throwable?) {
        i(clazz.simpleName, msg, t)
    }

    fun i(tag: String?, msg: String?) {
        if (isInfoLogEnable) {
            Log.i(tag, msg!!)
        }
    }

    fun i(tag: String?, msg: String?, t: Throwable?) {
        if (isInfoLogEnable) {
            Log.i(tag, msg, t)
        }
    }

    fun i(tag: String?, msg: String?, vararg args: Any?) {
        i(tag, formatString(msg, *args))
    }

    fun v(clazz: Class<*>, msg: String?) {
        v(clazz.simpleName, msg)
    }

    fun v(clazz: Class<*>, msg: String?, t: Throwable?) {
        v(clazz.simpleName, msg, t)
    }

    fun v(tag: String?, msg: String?) {
        if (isVerboseLogEnable) {
            Log.v(tag, msg!!)
        }
    }

    fun v(tag: String?, msg: String?, t: Throwable?) {
        if (isVerboseLogEnable) {
            Log.v(tag, msg, t)
        }
    }

    fun v(tag: String?, msg: String?, vararg args: Any?) {
        v(tag, formatString(msg, *args))
    }

    fun w(clazz: Class<*>, msg: String?) {
        w(clazz.simpleName, msg)
    }

    fun w(clazz: Class<*>, msg: String?, t: Throwable?) {
        w(clazz.simpleName, msg, t)
    }

    fun w(tag: String?, msg: String?) {
        if (isWarnLogEnable) {
            Log.w(tag, msg!!)
        }
    }

    fun w(tag: String?, msg: String?, t: Throwable?) {
        if (isWarnLogEnable) {
            Log.w(tag, msg, t)
        }
    }

    fun w(tag: String?, msg: String?, vararg args: Any?) {
        w(tag, formatString(msg, *args))
    }

    fun json(tag: String?, jsonStr: String) {
        if (isCustomLogEnable) {
            if (TextUtils.isEmpty(jsonStr)) {
                return
            }
            try {
                if (jsonStr.startsWith("{")) {
                    json(tag, JSONObject(jsonStr))
                } else if (jsonStr.startsWith("[")) {
                    json(tag, JSONArray(jsonStr))
                } else {
                    i(tag, jsonStr)
                }
            } catch (e: Exception) {
                printStackTrace(e)
            }
        }
    }

    fun json(tag: String?, jsonObject: JSONObject) {
        if (isCustomLogEnable) {
            try {
                val formattedJsonObject = jsonObject.toString(4)
                if (formattedJsonObject.length > 2048) {
                    var i = 0
                    while (i < formattedJsonObject.length) {
                        e(
                            tag,
                            formattedJsonObject.substring(
                                i,
                                Math.min(i + 2048, formattedJsonObject.length)
                            )
                        )
                        i += 2048
                    }
                } else {
                    i(tag, formattedJsonObject)
                }
            } catch (e: Exception) {
                printStackTrace(e)
            }
        }
    }

    fun json(tag: String?, jsonArray: JSONArray) {
        if (isCustomLogEnable) {
            try {
                val formattedJsonObject = jsonArray.toString(4)
                if (formattedJsonObject.length > 2048) {
                    var i = 0
                    while (i < formattedJsonObject.length) {
                        e(
                            tag,
                            formattedJsonObject.substring(
                                i,
                                Math.min(i + 2048, formattedJsonObject.length)
                            )
                        )
                        i += 2048
                    }
                } else {
                    i(tag, formattedJsonObject)
                }
            } catch (e: Exception) {
                printStackTrace(e)
            }
        }
    }

    fun isLoggable(tag: Class<*>, level: Int): Boolean {
        return Log.isLoggable(tag.simpleName, level)
    }

    fun logCursor(tag: Class<*>, cursor: Cursor?) {
        if (isCustomLogEnable) {
            if (cursor != null) {
                val count = cursor.columnCount
                if (count == 0) {
                    Log.e(tag.simpleName, "The cursor is empty")
                }
                val sb = StringBuilder("Cursor Column")
                for (i in 0 until count) {
                    sb.append(cursor.getColumnName(i)).append("\t")
                }
                Log.i(tag.simpleName, sb.toString())
                if (cursor.moveToFirst()) {
                    do {
                        sb.delete(0, sb.length)
                        sb.append("Cursor Data)")
                        for (i in 0 until count) {
                            sb.append(cursor.getString(i)).append("\t")
                        }
                        Log.i(tag.simpleName, sb.toString())
                    } while (cursor.moveToNext())
                }
            } else {
                Log.e(tag.simpleName, "The cursor is null")
            }
        }
    }

    fun logBundle(b: Bundle?) {
        if (isCustomLogEnable) {
            if (b != null) {
                val set = b.keySet()
                if (set.size > 0) {
                    for (key in set) {
                        val obj = b[key]
                        if (obj != null) {
                            Log.e("Bundle extra", "key = $key value = $obj")
                        }
                    }
                }
            }
        }
    }

    fun logCollection(tag: String?, collection: Collection<*>?) {
        if (isCustomLogEnable) {
            if (collection != null) {
                Log.d(tag, collection.toString())
            }
        }
    }

    @TargetApi(4)
    fun logIntent(it: Intent?) {
        if (isCustomLogEnable) {
            if (it != null) {
                Log.e("Intent", "Package " + it.getPackage())
                Log.e("Intent", "Action " + it.action)
                Log.e("Intent", "Flag " + it.flags)
                val payload = it.extras
                logBundle(payload)
            }
        }
    }

    fun logStackTrace(e: Exception) {
        e.printStackTrace()
    }

    fun printStackTrace(e: Exception) {
        if (isErrorLogEnable) {
            e.printStackTrace()
        }
    }

    fun getStackTraceString(tr: Throwable?): String {
        return Log.getStackTraceString(tr)
    }

    fun formatString(message: String?, vararg args: Any?): String? {
        return if (args.size == 0) message else String.format(message!!, *args)
    }

    fun startRecordFunc(tag: String?) {
        if (!isInfoLogEnable || tag == null) {
            return
        }
        val tm = System.nanoTime()
        synchronized(tmStarts) { tmStarts.put(tag, tm) }
    }

    fun stopRecordFunc(tag: String?) {
        if (!isInfoLogEnable || tag == null) {
            return
        }
        val tm = System.nanoTime()
        var start: Long = 0
        synchronized(tmStarts) {
            val v = tmStarts.remove(tag)
            if (v != null) {
                start = v
            }
        }
        if (start > 0) {
            i("FuncTime", tag + " time: " + (tm - start) / 1000000L + "ms")
        } else {
            i("FuncTime", "$tag wrong functime, not call startRecordFunc")
        }
    }

    fun startTimerLog(key: String?, label: String?) {
        if (!isInfoLogEnable || key == null || label == null) {
            return
        }
        if (timeLoggers.containsKey(key)) {
            e("startTimerLog", "The key \"$key\" is already exist!")
            return
        }
        val logger = TimingLogger(key, label)
        timeLoggers[key] = logger
    }

    fun addSplit(key: String?, splitLabel: String?) {
        if (!isInfoLogEnable || key == null || splitLabel == null) {
            return
        }
        if (!timeLoggers.containsKey(key)) {
            e(
                "addSplit",
                "Add \"$splitLabel\" failed! Couldn't find key \"$key\", have you invoked startTimerLog()?"
            )
            return
        }
        val logger = timeLoggers[key]
        logger!!.addSplit(splitLabel)
    }

    fun stopTimerLog(key: String?) {
        if (!isInfoLogEnable || key == null) {
            return
        }
        if (!timeLoggers.containsKey(key)) {
            e("stopTimerLog", "Couldn't find key \"$key\", have you invoked startTimerLog()?")
            return
        }
        val logger = timeLoggers[key]
        logger!!.dumpToLog()
        timeLoggers.remove(key)
    }

    var isDebug = BuildConfig.DEBUG // 是否需要打印bug，debug与release的buildconfig，也可以自定义
    private const val TAG = "WBG" //默认情况下的前缀
    private fun generateTag(): String {
//        StackTraceElement stack = Thread.currentThread().getStackTrace()[4];//此方法取得的栈的前两个分别为vm和Thread
        val stack = Throwable().stackTrace[2]
        var tag = "%s.%s(L:%d)"
        var className = stack.className
        className = className.substring(className.lastIndexOf(".") + 1)
        tag = String.format(tag, className, stack.methodName, stack.lineNumber)
        tag = if (TextUtils.isEmpty(TAG)) tag else TAG + ":" + tag
        return tag
    }

    // 下面四个是默认tag的函数
    fun i(msg: String?) {
        if (isDebug) Log.i(generateTag(), msg!!)
    }

    fun d(msg: String?) {
        if (isDebug) Log.d(generateTag(), msg!!)
    }

    fun e(msg: String?) {
        if (isDebug) Log.e(generateTag(), msg!!)
    }

    fun v(msg: String?) {
        if (isDebug) Log.v(generateTag(), msg!!)
    }
}